/*
 * Copyright (C) 2012 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <gtest/gtest.h>
#include "flutter/sky/engine/wtf/LinkedHashSet.h"
#include "flutter/sky/engine/wtf/ListHashSet.h"
#include "flutter/sky/engine/wtf/PassRefPtr.h"
#include "flutter/sky/engine/wtf/RefCounted.h"
#include "flutter/sky/engine/wtf/RefPtr.h"

namespace {

template <typename Set>
void removeFirstHelper() {
  Set list;
  list.add(-1);
  list.add(0);
  list.add(1);
  list.add(2);
  list.add(3);

  EXPECT_EQ(-1, list.first());
  EXPECT_EQ(3, list.last());

  list.removeFirst();
  EXPECT_EQ(0, list.first());

  list.removeLast();
  EXPECT_EQ(2, list.last());

  list.removeFirst();
  EXPECT_EQ(1, list.first());

  list.removeFirst();
  EXPECT_EQ(2, list.first());

  list.removeFirst();
  EXPECT_TRUE(list.isEmpty());
}

TEST(ListHashSetTest, RemoveFirst) {
  removeFirstHelper<ListHashSet<int>>();
  removeFirstHelper<ListHashSet<int, 1>>();
}

TEST(LinkedHashSetTest, RemoveFirst) {
  removeFirstHelper<LinkedHashSet<int>>();
}

template <typename Set>
void appendOrMoveToLastNewItems() {
  Set list;
  typename Set::AddResult result = list.appendOrMoveToLast(1);
  EXPECT_TRUE(result.isNewEntry);
  result = list.add(2);
  EXPECT_TRUE(result.isNewEntry);
  result = list.appendOrMoveToLast(3);
  EXPECT_TRUE(result.isNewEntry);

  EXPECT_EQ(list.size(), 3UL);

  // The list should be in order 1, 2, 3.
  typename Set::iterator iterator = list.begin();
  EXPECT_EQ(1, *iterator);
  ++iterator;
  EXPECT_EQ(2, *iterator);
  ++iterator;
  EXPECT_EQ(3, *iterator);
  ++iterator;
}

TEST(ListHashSetTest, AppendOrMoveToLastNewItems) {
  appendOrMoveToLastNewItems<ListHashSet<int>>();
  appendOrMoveToLastNewItems<ListHashSet<int, 1>>();
}

TEST(LinkedHashSetTest, AppendOrMoveToLastNewItems) {
  appendOrMoveToLastNewItems<LinkedHashSet<int>>();
}

template <typename Set>
void appendOrMoveToLastWithDuplicates() {
  Set list;

  // Add a single element twice.
  typename Set::AddResult result = list.add(1);
  EXPECT_TRUE(result.isNewEntry);
  result = list.appendOrMoveToLast(1);
  EXPECT_FALSE(result.isNewEntry);
  EXPECT_EQ(1UL, list.size());

  list.add(2);
  list.add(3);
  EXPECT_EQ(3UL, list.size());

  // Appending 2 move it to the end.
  EXPECT_EQ(3, list.last());
  result = list.appendOrMoveToLast(2);
  EXPECT_FALSE(result.isNewEntry);
  EXPECT_EQ(2, list.last());

  // Inverse the list by moving each element to end end.
  result = list.appendOrMoveToLast(3);
  EXPECT_FALSE(result.isNewEntry);
  result = list.appendOrMoveToLast(2);
  EXPECT_FALSE(result.isNewEntry);
  result = list.appendOrMoveToLast(1);
  EXPECT_FALSE(result.isNewEntry);
  EXPECT_EQ(3UL, list.size());

  typename Set::iterator iterator = list.begin();
  EXPECT_EQ(3, *iterator);
  ++iterator;
  EXPECT_EQ(2, *iterator);
  ++iterator;
  EXPECT_EQ(1, *iterator);
  ++iterator;
}

TEST(ListHashSetTest, AppendOrMoveToLastWithDuplicates) {
  appendOrMoveToLastWithDuplicates<ListHashSet<int>>();
  appendOrMoveToLastWithDuplicates<ListHashSet<int, 1>>();
}

TEST(LinkedHashSetTest, AppendOrMoveToLastWithDuplicates) {
  appendOrMoveToLastWithDuplicates<LinkedHashSet<int>>();
}

template <typename Set>
void prependOrMoveToFirstNewItems() {
  Set list;
  typename Set::AddResult result = list.prependOrMoveToFirst(1);
  EXPECT_TRUE(result.isNewEntry);
  result = list.add(2);
  EXPECT_TRUE(result.isNewEntry);
  result = list.prependOrMoveToFirst(3);
  EXPECT_TRUE(result.isNewEntry);

  EXPECT_EQ(list.size(), 3UL);

  // The list should be in order 3, 1, 2.
  typename Set::iterator iterator = list.begin();
  EXPECT_EQ(3, *iterator);
  ++iterator;
  EXPECT_EQ(1, *iterator);
  ++iterator;
  EXPECT_EQ(2, *iterator);
  ++iterator;
}

TEST(ListHashSetTest, PrependOrMoveToFirstNewItems) {
  prependOrMoveToFirstNewItems<ListHashSet<int>>();
  prependOrMoveToFirstNewItems<ListHashSet<int, 1>>();
}

TEST(LinkedHashSetTest, PrependOrMoveToFirstNewItems) {
  prependOrMoveToFirstNewItems<LinkedHashSet<int>>();
}

template <typename Set>
void prependOrMoveToLastWithDuplicates() {
  Set list;

  // Add a single element twice.
  typename Set::AddResult result = list.add(1);
  EXPECT_TRUE(result.isNewEntry);
  result = list.prependOrMoveToFirst(1);
  EXPECT_FALSE(result.isNewEntry);
  EXPECT_EQ(1UL, list.size());

  list.add(2);
  list.add(3);
  EXPECT_EQ(3UL, list.size());

  // Prepending 2 move it to the beginning.
  EXPECT_EQ(1, list.first());
  result = list.prependOrMoveToFirst(2);
  EXPECT_FALSE(result.isNewEntry);
  EXPECT_EQ(2, list.first());

  // Inverse the list by moving each element to the first position.
  result = list.prependOrMoveToFirst(1);
  EXPECT_FALSE(result.isNewEntry);
  result = list.prependOrMoveToFirst(2);
  EXPECT_FALSE(result.isNewEntry);
  result = list.prependOrMoveToFirst(3);
  EXPECT_FALSE(result.isNewEntry);
  EXPECT_EQ(3UL, list.size());

  typename Set::iterator iterator = list.begin();
  EXPECT_EQ(3, *iterator);
  ++iterator;
  EXPECT_EQ(2, *iterator);
  ++iterator;
  EXPECT_EQ(1, *iterator);
  ++iterator;
}

TEST(ListHashSetTest, PrependOrMoveToLastWithDuplicates) {
  prependOrMoveToLastWithDuplicates<ListHashSet<int>>();
  prependOrMoveToLastWithDuplicates<ListHashSet<int, 1>>();
}

TEST(LinkedHashSetTest, PrependOrMoveToLastWithDuplicates) {
  prependOrMoveToLastWithDuplicates<LinkedHashSet<int>>();
}

class DummyRefCounted : public WTF::RefCounted<DummyRefCounted> {
 public:
  DummyRefCounted(bool& isDeleted) : m_isDeleted(isDeleted) {
    m_isDeleted = false;
  }
  ~DummyRefCounted() { m_isDeleted = true; }
  void ref() {
    WTF::RefCounted<DummyRefCounted>::ref();
    ++m_refInvokesCount;
  }

  static int m_refInvokesCount;

 private:
  bool& m_isDeleted;
};

int DummyRefCounted::m_refInvokesCount = 0;

template <typename Set>
void withRefPtr() {
  bool isDeleted = false;
  DummyRefCounted::m_refInvokesCount = 0;
  RefPtr<DummyRefCounted> ptr = adoptRef(new DummyRefCounted(isDeleted));
  EXPECT_EQ(0, DummyRefCounted::m_refInvokesCount);

  Set set;
  set.add(ptr);
  // Referenced only once (to store a copy in the container).
  EXPECT_EQ(1, DummyRefCounted::m_refInvokesCount);
  EXPECT_EQ(ptr, set.first());
  EXPECT_EQ(1, DummyRefCounted::m_refInvokesCount);

  DummyRefCounted* rawPtr = ptr.get();

  EXPECT_TRUE(set.contains(ptr));
  EXPECT_TRUE(set.contains(rawPtr));
  EXPECT_EQ(1, DummyRefCounted::m_refInvokesCount);

  ptr.clear();
  EXPECT_FALSE(isDeleted);
  EXPECT_EQ(1, DummyRefCounted::m_refInvokesCount);

  set.remove(rawPtr);
  EXPECT_TRUE(isDeleted);

  EXPECT_EQ(1, DummyRefCounted::m_refInvokesCount);
}

TEST(ListHashSetTest, WithRefPtr) {
  withRefPtr<ListHashSet<RefPtr<DummyRefCounted>>>();
  withRefPtr<ListHashSet<RefPtr<DummyRefCounted>, 1>>();
}

TEST(LinkedHashSetTest, WithRefPtr) {
  withRefPtr<LinkedHashSet<RefPtr<DummyRefCounted>>>();
}

template <typename Set, typename SetRef, typename Iterator>
void findHelper() {
  Set set;
  set.add(-1);
  set.add(0);
  set.add(1);
  set.add(2);
  set.add(3);

  SetRef ref = set;
  Iterator it = ref.find(2);
  EXPECT_EQ(2, *it);
  ++it;
  EXPECT_EQ(3, *it);
  --it;
  --it;
  EXPECT_EQ(1, *it);
}

TEST(ListHashSetTest, Find) {
  findHelper<ListHashSet<int>, const ListHashSet<int>&,
             ListHashSet<int>::const_iterator>();
  findHelper<ListHashSet<int>, ListHashSet<int>&, ListHashSet<int>::iterator>();
  findHelper<ListHashSet<int, 1>, const ListHashSet<int, 1>&,
             ListHashSet<int, 1>::const_iterator>();
  findHelper<ListHashSet<int, 1>, ListHashSet<int, 1>&,
             ListHashSet<int, 1>::iterator>();
}

TEST(LinkedHashSetTest, Find) {
  findHelper<LinkedHashSet<int>, const LinkedHashSet<int>&,
             LinkedHashSet<int>::const_iterator>();
  findHelper<LinkedHashSet<int>, LinkedHashSet<int>&,
             LinkedHashSet<int>::iterator>();
}

template <typename Set>
void insertBeforeHelper(bool canModifyWhileIterating) {
  Set set;
  set.add(-1);
  set.add(0);
  set.add(2);
  set.add(3);

  typename Set::iterator it = set.find(2);
  EXPECT_EQ(2, *it);
  set.insertBefore(it, 1);
  if (!canModifyWhileIterating)
    it = set.find(2);
  ++it;
  EXPECT_EQ(3, *it);
  EXPECT_EQ(5u, set.size());
  --it;
  --it;
  EXPECT_EQ(1, *it);
  if (canModifyWhileIterating) {
    set.remove(-1);
    set.remove(0);
    set.remove(2);
    set.remove(3);
    EXPECT_EQ(1u, set.size());
    EXPECT_EQ(1, *it);
    ++it;
    EXPECT_EQ(it, set.end());
    --it;
    EXPECT_EQ(1, *it);
    set.insertBefore(it, -1);
    set.insertBefore(it, 0);
    set.add(2);
    set.add(3);
  }
  set.insertBefore(2, 42);
  set.insertBefore(-1, 103);
  EXPECT_EQ(103, set.first());
  if (!canModifyWhileIterating)
    it = set.find(1);
  ++it;
  EXPECT_EQ(42, *it);
  EXPECT_EQ(7u, set.size());
}

TEST(ListHashSetTest, InsertBefore) {
  insertBeforeHelper<ListHashSet<int>>(true);
  insertBeforeHelper<ListHashSet<int, 1>>(true);
}

TEST(LinkedHashSetTest, InsertBefore) {
  insertBeforeHelper<LinkedHashSet<int>>(false);
}

template <typename Set>
void addReturnIterator(bool canModifyWhileIterating) {
  Set set;
  set.add(-1);
  set.add(0);
  set.add(1);
  set.add(2);

  typename Set::iterator it = set.addReturnIterator(3);
  EXPECT_EQ(3, *it);
  --it;
  EXPECT_EQ(2, *it);
  EXPECT_EQ(5u, set.size());
  --it;
  EXPECT_EQ(1, *it);
  --it;
  EXPECT_EQ(0, *it);
  it = set.addReturnIterator(4);
  if (canModifyWhileIterating) {
    set.remove(3);
    set.remove(2);
    set.remove(1);
    set.remove(0);
    set.remove(-1);
    EXPECT_EQ(1u, set.size());
  }
  EXPECT_EQ(4, *it);
  ++it;
  EXPECT_EQ(it, set.end());
  --it;
  EXPECT_EQ(4, *it);
  if (canModifyWhileIterating) {
    set.insertBefore(it, -1);
    set.insertBefore(it, 0);
    set.insertBefore(it, 1);
    set.insertBefore(it, 2);
    set.insertBefore(it, 3);
  }
  EXPECT_EQ(6u, set.size());
  it = set.addReturnIterator(5);
  EXPECT_EQ(7u, set.size());
  set.remove(it);
  EXPECT_EQ(6u, set.size());
  EXPECT_EQ(4, set.last());
}

TEST(ListHashSetTest, AddReturnIterator) {
  addReturnIterator<ListHashSet<int>>(true);
  addReturnIterator<ListHashSet<int, 1>>(true);
}

TEST(LinkedHashSetTest, AddReturnIterator) {
  addReturnIterator<LinkedHashSet<int>>(false);
}

template <typename Set>
void excerciseValuePeekInType() {
  Set set;
  bool isDeleted = false;
  bool isDeleted2 = false;

  RefPtr<DummyRefCounted> ptr = adoptRef(new DummyRefCounted(isDeleted));
  RefPtr<DummyRefCounted> ptr2 = adoptRef(new DummyRefCounted(isDeleted2));

  typename Set::AddResult addResult = set.add(ptr);
  EXPECT_TRUE(addResult.isNewEntry);
  set.find(ptr);
  const Set& constSet(set);
  constSet.find(ptr);
  EXPECT_TRUE(set.contains(ptr));
  typename Set::iterator it = set.addReturnIterator(ptr);
  set.appendOrMoveToLast(ptr);
  set.prependOrMoveToFirst(ptr);
  set.insertBefore(ptr, ptr);
  set.insertBefore(it, ptr);
  EXPECT_EQ(1u, set.size());
  set.add(ptr2);
  ptr2.clear();
  set.remove(ptr);

  EXPECT_FALSE(isDeleted);
  ptr.clear();
  EXPECT_TRUE(isDeleted);

  EXPECT_FALSE(isDeleted2);
  set.removeFirst();
  EXPECT_TRUE(isDeleted2);

  EXPECT_EQ(0u, set.size());
}

TEST(ListHashSetTest, ExcerciseValuePeekInType) {
  excerciseValuePeekInType<ListHashSet<RefPtr<DummyRefCounted>>>();
  excerciseValuePeekInType<ListHashSet<RefPtr<DummyRefCounted>, 1>>();
}

TEST(LinkedHashSetTest, ExcerciseValuePeekInType) {
  excerciseValuePeekInType<LinkedHashSet<RefPtr<DummyRefCounted>>>();
}

struct Simple {
  Simple(int value) : m_value(value){};
  int m_value;
};

struct Complicated {
  Complicated(int value) : m_simple(value) { s_objectsConstructed++; }

  Complicated(const Complicated& other) : m_simple(other.m_simple) {
    s_objectsConstructed++;
  }

  Simple m_simple;
  static int s_objectsConstructed;

 private:
  Complicated();
};

int Complicated::s_objectsConstructed = 0;

struct ComplicatedHashFunctions {
  static unsigned hash(const Complicated& key) { return key.m_simple.m_value; }
  static bool equal(const Complicated& a, const Complicated& b) {
    return a.m_simple.m_value == b.m_simple.m_value;
  }
};
struct ComplexityTranslator {
  static unsigned hash(const Simple& key) { return key.m_value; }
  static bool equal(const Complicated& a, const Simple& b) {
    return a.m_simple.m_value == b.m_value;
  }
};

template <typename Set>
void translatorTest() {
  Set set;
  set.add(Complicated(42));
  int baseLine = Complicated::s_objectsConstructed;

  typename Set::iterator it =
      set.template find<ComplexityTranslator>(Simple(42));
  EXPECT_NE(it, set.end());
  EXPECT_EQ(baseLine, Complicated::s_objectsConstructed);

  it = set.template find<ComplexityTranslator>(Simple(103));
  EXPECT_EQ(it, set.end());
  EXPECT_EQ(baseLine, Complicated::s_objectsConstructed);

  const Set& constSet(set);

  typename Set::const_iterator constIterator =
      constSet.template find<ComplexityTranslator>(Simple(42));
  EXPECT_NE(constIterator, constSet.end());
  EXPECT_EQ(baseLine, Complicated::s_objectsConstructed);

  constIterator = constSet.template find<ComplexityTranslator>(Simple(103));
  EXPECT_EQ(constIterator, constSet.end());
  EXPECT_EQ(baseLine, Complicated::s_objectsConstructed);
}

TEST(ListHashSetTest, ComplexityTranslator) {
  translatorTest<ListHashSet<Complicated, 256, ComplicatedHashFunctions>>();
  translatorTest<ListHashSet<Complicated, 1, ComplicatedHashFunctions>>();
}

TEST(LinkedHashSetTest, ComplexityTranslator) {
  translatorTest<LinkedHashSet<Complicated, ComplicatedHashFunctions>>();
}

struct Dummy {
  Dummy(bool& deleted) : deleted(deleted) {}

  ~Dummy() { deleted = true; }

  bool& deleted;
};

TEST(ListHashSetTest, WithOwnPtr) {
  bool deleted1 = false, deleted2 = false;

  typedef ListHashSet<OwnPtr<Dummy>> OwnPtrSet;
  OwnPtrSet set;

  Dummy* ptr1 = new Dummy(deleted1);
  {
    // AddResult in a separate scope to avoid assertion hit,
    // since we modify the container further.
    OwnPtrSet::AddResult res1 = set.add(adoptPtr(ptr1));
    EXPECT_EQ(res1.storedValue->m_value.get(), ptr1);
  }

  EXPECT_FALSE(deleted1);
  EXPECT_EQ(1UL, set.size());
  OwnPtrSet::iterator it1 = set.find(ptr1);
  EXPECT_NE(set.end(), it1);
  EXPECT_EQ(ptr1, (*it1));

  Dummy* ptr2 = new Dummy(deleted2);
  {
    OwnPtrSet::AddResult res2 = set.add(adoptPtr(ptr2));
    EXPECT_EQ(res2.storedValue->m_value.get(), ptr2);
  }

  EXPECT_FALSE(deleted2);
  EXPECT_EQ(2UL, set.size());
  OwnPtrSet::iterator it2 = set.find(ptr2);
  EXPECT_NE(set.end(), it2);
  EXPECT_EQ(ptr2, (*it2));

  set.remove(ptr1);
  EXPECT_TRUE(deleted1);

  set.clear();
  EXPECT_TRUE(deleted2);
  EXPECT_TRUE(set.isEmpty());

  deleted1 = false;
  deleted2 = false;
  {
    OwnPtrSet set;
    set.add(adoptPtr(new Dummy(deleted1)));
    set.add(adoptPtr(new Dummy(deleted2)));
  }
  EXPECT_TRUE(deleted1);
  EXPECT_TRUE(deleted2);

  deleted1 = false;
  deleted2 = false;
  OwnPtr<Dummy> ownPtr1;
  OwnPtr<Dummy> ownPtr2;
  ptr1 = new Dummy(deleted1);
  ptr2 = new Dummy(deleted2);
  {
    OwnPtrSet set;
    set.add(adoptPtr(ptr1));
    set.add(adoptPtr(ptr2));
    ownPtr1 = set.takeFirst();
    EXPECT_EQ(1UL, set.size());
    ownPtr2 = set.take(ptr2);
    EXPECT_TRUE(set.isEmpty());
  }
  EXPECT_FALSE(deleted1);
  EXPECT_FALSE(deleted2);

  EXPECT_EQ(ptr1, ownPtr1);
  EXPECT_EQ(ptr2, ownPtr2);
}

template <typename Set>
void swapTestHelper() {
  int num = 10;
  Set set0;
  Set set1;
  Set set2;
  for (int i = 0; i < num; ++i) {
    set1.add(i + 1);
    set2.add(num - i);
  }

  typename Set::iterator it1 = set1.begin();
  typename Set::iterator it2 = set2.begin();
  for (int i = 0; i < num; ++i, ++it1, ++it2) {
    EXPECT_EQ(*it1, i + 1);
    EXPECT_EQ(*it2, num - i);
  }
  EXPECT_EQ(set0.begin(), set0.end());
  EXPECT_EQ(it1, set1.end());
  EXPECT_EQ(it2, set2.end());

  // Shift sets: 2->1, 1->0, 0->2
  set1.swap(set2);  // Swap with non-empty sets.
  set0.swap(set2);  // Swap with an empty set.

  it1 = set0.begin();
  it2 = set1.begin();
  for (int i = 0; i < num; ++i, ++it1, ++it2) {
    EXPECT_EQ(*it1, i + 1);
    EXPECT_EQ(*it2, num - i);
  }
  EXPECT_EQ(it1, set0.end());
  EXPECT_EQ(it2, set1.end());
  EXPECT_EQ(set2.begin(), set2.end());

  int removedIndex = num >> 1;
  set0.remove(removedIndex + 1);
  set1.remove(num - removedIndex);

  it1 = set0.begin();
  it2 = set1.begin();
  for (int i = 0; i < num; ++i, ++it1, ++it2) {
    if (i == removedIndex)
      ++i;
    EXPECT_EQ(*it1, i + 1);
    EXPECT_EQ(*it2, num - i);
  }
  EXPECT_EQ(it1, set0.end());
  EXPECT_EQ(it2, set1.end());
}

TEST(ListHashSetTest, Swap) {
  swapTestHelper<ListHashSet<int>>();
}

TEST(LinkedHashSetTest, Swap) {
  swapTestHelper<LinkedHashSet<int>>();
}

}  // namespace
